﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

#if !(V1_1 || V2 || V3 || V3_5)
using System.Dynamic;
using System.Linq.Expressions;
#endif

namespace V8.Net
{
    // ========================================================================================================================

    /// <summary>
    /// V8NativeObject对象的接口。
    /// </summary>
    public interface IV8NativeObject : IV8Object, IDisposable, IDynamicMetaObjectProvider
    {
        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 创建对象实例并设置V8Engine属性后立即调用.
        /// 注意:如果需要,重写'Dispose()'而不是实现析构函数(finalizer).
        /// </summary>
        void Initialize(V8NativeObject owner);

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 在没有更多引用且对象已准备好处置时调用.如果您需要自己处置任何本机资源,则应始终覆盖此设置.不要依赖析构函数(finalizer)
        /// 注意:这可以从终结器线程中调用(通过V8.NET间接调用),在这种情况下,'GC.SuppressFinalize()'将在返回时自动调用（以防止在本机端也准备好之前进行收集)
        /// 如果覆盖,请确保回调此基本方法.
        /// </summary>
        new void Dispose();

        // --------------------------------------------------------------------------------------------------------------------
    }

    /// <summary>
    /// 表示一个基本的JavaScript对象.此类包装V8功能，以便对任何本机V8对象(包括托管对象)进行所需的操作
    /// 此类实现了'DynamicObject',以使设置属性更加容易
    /// </summary>
    public unsafe class V8NativeObject : IHandleBased, IV8NativeObject, IDynamicMetaObjectProvider, IFinalizable
    {
        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 对拥有此对象的V8Engine实例的引用.'V8NativeObject'的默认实现是缓存并返回'base.Engine',因为它继承自'Handle'
        /// </summary>
        public V8Engine Engine { get { return _Engine ?? (_Engine = _Handle.Engine); } }
        internal V8Engine _Engine;

        public Handle AsHandle() { return _Handle; }
        public InternalHandle AsInternalHandle { get { return _Handle._Handle; } }
        public V8NativeObject Object { get { return this; } }

        /// <summary>
        /// 与此对象关联的V8.NET ObjectTemplate或FunctionTemplate实例(如果有);如果未使用V8.NET模板创建此对象,则为null
        /// </summary>
        public ITemplate Template
        {
            get { return _Template; }
            internal set
            {
                if (_Template != null) ((ITemplateInternal)_Template)._ReferenceCount--;
                _Template = value;
                if (_Template != null) ((ITemplateInternal)_Template)._ReferenceCount++;
            }
        }
        ITemplate _Template;

        /// <summary>
        /// V8.NET托管对象ID,用于在本机端和托管端都跟踪此对象实例
        /// </summary>
        public Int32 ID
        {
            get { var id = _Handle.ObjectID; return id < 0 ? _ID ?? id : id; } // (这将尝试返回句柄代理的基础管理对象ID，或者如果为-1，则返回本地ID)
            internal set { _Handle.ObjectID = (_ID = value).Value; } // (一旦设置，只要基础句柄的托管对象ID为-1，托管对象将固定为该ID)
        }
        Int32? _ID;

        /// <summary>
        /// 同一接口的另一个对象,用于将操作定向到(例如'Initialize()').
        /// 如果使用通用类型'V8NativeObject T>',则将其设置为'T'的实例,否则将其设置为'this'的实例
        /// </summary>
        public IV8NativeObject Proxy { get { return _Proxy; } }
        internal IV8NativeObject _Proxy;

        /// <summary>
        /// 如果此对象已初始化并且可以使用,则为true
        /// </summary>
        public bool IsInitilized { get; internal set; }

        /// <summary>
        /// 对托管对象句柄的引用,该引用包装了此托管对象的本机V8句柄.
        /// 'V8NativeObject'的默认实现是返回自身,因为它继承自' Handle'.
        /// 设置此属性将调用继承的' Set()'方法来替换与此对象实例相关联的句柄
        /// (永远不要在通过模板创建的对象('V8ManagedObject'对象)上执行此操作,否则从JavaScript到托管方的回调将不会 采取行动,如果有的话)
        /// </summary>
        public ObjectHandle Handle { get { return _Handle; } set { _Handle.Set((Handle)value); } }
        internal ObjectHandle _Handle = ObjectHandle.Empty;

#if !(V1_1 || V2 || V3 || V3_5)
        /// <summary>
        ///返回对此对象的'动态'引用(它只是句柄实例,具有动态支持)
        /// </summary>
        public virtual dynamic AsDynamic { get { return _Handle; } }
#endif

        /// <summary>
        /// 对象的原型(每个JavaScript对象都隐含一个原型)
        /// </summary>
        public ObjectHandle Prototype
        {
            get
            {
                if (_Prototype == null && _Handle.IsObjectType)
                {
                    // ... 原型尚未设置，因此获取原型并将其包装起来
                    _Prototype = _Handle.Prototype;
                }

                return _Prototype;
            }
        }
        internal ObjectHandle _Prototype;

        /// <summary>
        /// Returns true if this object is ready to be garbage collected by the native side.
        /// </summary>
        public bool IsManagedObjectWeak { get { lock (Engine._Objects) { return _ID != null ? Engine._Objects[_ID.Value].IsGCReady : true; } } }

        /// <summary>
        /// Used internally to quickly determine when an instance represents a binder object type, or static type binder function (faster than reflection!).
        /// </summary>
        public BindingMode BindingType { get { return _BindingMode; } }
        internal BindingMode _BindingMode;

        // --------------------------------------------------------------------------------------------------------------------

        public virtual InternalHandle this[string propertyName]
        {
            get
            {
                return _Handle.GetProperty(propertyName);
            }
            set
            {
                _Handle.SetProperty(propertyName, value);
            }
        }

        public virtual InternalHandle this[int index]
        {
            get
            {
                return _Handle.GetProperty(index);
            }
            set
            {
                _Handle.SetProperty(index, value);
            }
        }

        // --------------------------------------------------------------------------------------------------------------------

        public V8NativeObject()
        {
            _Proxy = this;
        }

        public V8NativeObject(IV8NativeObject proxy)
        {
            _Proxy = proxy ?? this;
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// Called immediately after creating an object instance and setting the V8Engine property.
        /// Derived objects should override this for construction instead of using the constructor.
        /// In the constructor, the object only exists as an empty shell.
        /// It's ok to setup non-v8 values in constructors, but be careful not to trigger any calls into the V8Engine itself.
        /// <para>Note: Because this method is virtual, it does not guarantee that 'IsInitialized' will be considered.  Implementations should check against
        /// the 'IsInitilized' property.</para>
        /// </summary>
        public virtual void Initialize()
        {
            if (_Proxy != this && !IsInitilized)
                _Proxy.Initialize(this);

            IsInitilized = true;
        }

        /// <summary>
        /// (Exists only to support the 'IV8NativeInterface' interface and should not be called directly - call 'Initialize()' instead.)
        /// </summary>
        public void Initialize(V8NativeObject owner)
        {
            if (!IsInitilized)
                Initialize();
        }

        /// <summary>
        /// This is called on the GC finalizer thread to flag that this managed object entry can be collected.
        /// <para>Note: There are no longer any managed references to the object at this point; HOWEVER, there may still be NATIVE ones.
        /// This means the object may survive this process, at which point it's up to the worker thread to clean it up when the native V8 GC is ready.</para>
        /// </summary>
        ~V8NativeObject()
        {
            if (_ID != null) // (if there's no ID, then let the instance die)
            {
                lock (Engine._Objects)
                {
                    Engine._Objects[_ID.Value].DoFinalize(this); // (will re-register this object for finalization)
                    // (the native GC has not reported we can remove this yet, so just flag the collection attempt [note: if 'Engine._Objects[_ID.Value].CanCollect' is true here, then finalize will succeed])
                }
            }

            if (!((IFinalizable)this).CanFinalize)
                lock (_Engine._ObjectsToFinalize)
                {
                    _Engine._ObjectsToFinalize.Add(this);
                    GC.ReRegisterForFinalize(this);
                }
        }

        bool IFinalizable.CanFinalize { get { return _ID == null; } set { } }

        void IFinalizable.DoFinalize()
        {
            if (_Handle.IsWeakHandle) // (a handle is weak when there is only one reference [itself], which means this object is ready for the worker)
                _TryDisposeNativeHandle();
        }

        /// <summary>
        /// Called when there are no more references (on either the managed or native side) and the object is ready to be deleted from the V8.NET system.
        /// You should never call this from code directly unless you need to force the release of native resources associated with a custom implementation
        /// (and if so, a custom internal flag should be kept indicating whether or not the resources have been disposed).
        /// You should always override/implement this if you need to dispose of any native resources in custom implementations.
        /// DO NOT rely on the destructor (finalizer) - the object can still survive it.
        /// <para>Note: This can be triggered either by the worker thread, or on the call-back from the V8 garbage collector.  In either case, tread it as if
        /// it was called from the GC finalizer (not on the main thread).</para>
        /// *** If overriding, DON'T call back to this method, otherwise it will call back and end up in a cyclical call (and a stack overflow!). ***
        /// </summary>
        public virtual void Dispose()
        {
            if (_Proxy != this) _Proxy.Dispose();
        }

        /// <summary>
        /// This is called automatically when both the handle AND reference for the managed object are weak [no longer in use], in which case this object
        /// info instance is ready to be removed.
        /// </summary>
        internal void _TryDisposeNativeHandle()
        {
            if (IsManagedObjectWeak && (_Handle.IsEmpty || _Handle.IsWeakHandle && !_Handle.IsInPendingDisposalQueue))
            {
                _Handle.IsInPendingDisposalQueue = true; // (this also helps to make sure this method isn't called again)

                lock (_Engine._WeakObjects)
                {
                    _Engine._WeakObjects.Add(_ID.Value); // (queue on the worker to set a native weak reference to dispose of this object later when the native GC is ready)
                }
            }
        }

        /// <summary>
        /// Called by the worker thread to make the native handle weak.  Once the native GC attempts to collect the underlying native object, then
        /// '_OnNativeGCRequested()' will get called to finalize the disposal of the managed object.
        /// </summary>
        internal void _MakeWeak()
        {
            V8NetProxy.MakeWeakHandle(_Handle);
            // (once the native V8 engine agrees, this instance will be removed due to a global GC callback registered when the engine was created)
        }

        internal bool _OnNativeGCRequested() // WARNING: The worker thread may trigger a V8 GC callback in its own thread!
        {
            lock (_Engine._Objects)
            {
                if (Template is FunctionTemplate)
                    ((FunctionTemplate)Template)._RemoveFunctionType(ID);// (make sure to remove the function references from the template instance)

                Dispose(); // (notify any custom dispose methods to clean up)

                GC.SuppressFinalize(this); // (required otherwise the object's finalizer will be triggered again)

                _Handle.Dispose(); // (note: this may already be disposed, in which case this call does nothing)

                Engine._ClearAccessors(_ID.Value); // (just to be sure - accessors are no longer needed once the native handle is GC'd)

                Template = null;

                if (_ID != null)
                    _Engine._RemoveObjectWeakReference(_ID.Value);

                _Handle.ObjectID = -1;

                _ID = null;
            }

            return true; // ("true" means to "continue disposal of native handle" [if not already empty])
        }

        // --------------------------------------------------------------------------------------------------------------------

        public static implicit operator InternalHandle(V8NativeObject obj) { return obj != null ? obj._Handle._Handle : InternalHandle.Empty; }
        public static implicit operator Handle(V8NativeObject obj) { return obj != null ? obj._Handle : ObjectHandle.Empty; }
        public static implicit operator ObjectHandle(V8NativeObject obj) { return obj != null ? obj._Handle : ObjectHandle.Empty; }

        // --------------------------------------------------------------------------------------------------------------------

        public override string ToString()
        {
            var objText = _Proxy.GetType().Name;
            var disposeText = _Handle.IsDisposed ? "Yes" : "No";
            return objText + " (ID: " + (_ID ?? -1) + " / Value: '" + _Handle + "' / Is Disposed?: " + disposeText + ")";
        }

        // --------------------------------------------------------------------------------------------------------------------

#if !(V1_1 || V2 || V3 || V3_5)
        public DynamicMetaObject GetMetaObject(Expression parameter)
        {
            return new DynamicHandle(this, parameter);
        }
#endif

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// Calls the V8 'Set()' function on the underlying native object.
        /// Returns true if successful.
        /// </summary>
        public virtual bool SetProperty(string name, InternalHandle value, V8PropertyAttributes attributes = V8PropertyAttributes.None)
        {
            return _Handle._Handle.SetProperty(name, value, attributes);
        }

        /// <summary>
        /// Calls the V8 'Set()' function on the underlying native object.
        /// Returns true if successful.
        /// </summary>
        public virtual bool SetProperty(Int32 index, InternalHandle value)
        {
            return _Handle._Handle.SetProperty(index, value);
        }

        /// <summary>
        /// Sets a property to a given object. If the object is not V8.NET related, then the system will attempt to bind the instance and all public members to
        /// the specified property name.
        /// Returns true if successful.
        /// </summary>
        /// <param name="name">The property name.</param>
        /// <param name="obj">Some value or object instance. 'Engine.CreateValue()' will be used to convert value types.</param>
        /// <param name="className">A custom in-script function name for the specified object type, or 'null' to use either the type name as is (the default) or any existing 'ScriptObject' attribute name.</param>
        /// <param name="recursive">For object instances, if true, then object reference members are included, otherwise only the object itself is bound and returned.
        /// For security reasons, public members that point to object instances will be ignored. This must be true to included those as well, effectively allowing
        /// in-script traversal of the object reference tree (so make sure this doesn't expose sensitive methods/properties/fields).</param>
        /// <param name="memberSecurity">Flags that describe JavaScript properties.  They must be 'OR'd together as needed.</param>
        public bool SetProperty(string name, object obj, string className = null, bool? recursive = null, ScriptMemberSecurity? memberSecurity = null)
        {
            return _Handle._Handle.SetProperty(name, obj, className, recursive, memberSecurity);
        }

        /// <summary>
        /// Binds a 'V8Function' object to the specified type and associates the type name (or custom script name) with the underlying object.
        /// Returns true if successful.
        /// </summary>
        /// <param name="type">The type to wrap.</param>
        /// <param name="className">A custom in-script function name for the specified type, or 'null' to use either the type name as is (the default) or any existing 'ScriptObject' attribute name.</param>
        /// <param name="recursive">For object types, if true, then object reference members are included, otherwise only the object itself is bound and returned.
        /// For security reasons, public members that point to object instances will be ignored. This must be true to included those as well, effectively allowing
        /// in-script traversal of the object reference tree (so make sure this doesn't expose sensitive methods/properties/fields).</param>
        /// <param name="memberSecurity">Flags that describe JavaScript properties.  They must be 'OR'd together as needed.</param>
        public bool SetProperty(Type type, string className = null, bool? recursive = null, ScriptMemberSecurity? memberSecurity = null)
        {
            return _Handle._Handle.SetProperty(type, className, recursive, memberSecurity);
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// Calls the V8 'Get()' function on the underlying native object.
        /// If the property doesn't exist, the 'IsUndefined' property will be true.
        /// </summary>
        public virtual InternalHandle GetProperty(string name)
        {
            return _Handle._Handle.GetProperty(name);
        }

        /// <summary>
        /// Calls the V8 'Get()' function on the underlying native object.
        /// If the property doesn't exist, the 'IsUndefined' property will be true.
        /// </summary>
        public virtual InternalHandle GetProperty(Int32 index)
        {
            return _Handle._Handle.GetProperty(index);
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 在基础本机对象上调用V8'Delete()'函数.
        /// 如果属性已删除,则返回true
        /// </summary>
        public virtual bool DeleteProperty(string name)
        {
            return _Handle._Handle.GetProperty(name);
        }

        /// <summary>
        /// 在基础本机对象上调用V8'Delete()'函数.
        /// 如果属性已删除,则返回true
        /// </summary>
        public virtual bool DeleteProperty(Int32 index)
        {
            return _Handle._Handle.GetProperty(index);
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 在基础本机对象上调用V8'SetAccessor()'函数，以创建由'getter'和'setter'回调控制的属性
        /// </summary>
        public void SetAccessor(string name,
            V8NativeObjectPropertyGetter getter, V8NativeObjectPropertySetter setter,
            V8PropertyAttributes attributes = V8PropertyAttributes.None, V8AccessControl access = V8AccessControl.Default)
        {
            _Handle._Handle.SetAccessor(name, getter, setter, attributes, access);
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 返回此对象（包括原型链中的所有对象）的所有属性名称的列表
        /// </summary>
        public string[] GetPropertyNames()
        {
            return _Handle._Handle.GetPropertyNames();
        }

        /// <summary>
        /// 返回此对象的所有属性名称的列表（原型链除外）.
        /// </summary>
        public virtual string[] GetOwnPropertyNames()
        {
            return _Handle._Handle.GetOwnPropertyNames();
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// 获取此对象的属性的属性标志.如果属性不存在,则返回'V8PropertyAttributes.None'
        /// (注意:只有V8返回'None'.值'Undefined'具有内部代理含义，用于属性拦截).
        /// </summary>
        public V8PropertyAttributes GetPropertyAttributes(string name)
        {
            return _Handle._Handle.GetPropertyAttributes(name);
        }

        // --------------------------------------------------------------------------------------------------------------------

        /// <summary>
        ///调用指定对象上具有给定名称的对象属性作为函数,并返回结果.
        ///调用时,'_this'属性是函数内的'this'对象.如果函数名称为null或为空,则假定当前对象为函数对象
        /// </summary>
        public virtual InternalHandle Call(string functionName, InternalHandle _this, params InternalHandle[] args)
        {
            return _Handle._Handle.Call(functionName, _this, args);
        }

        /// <summary>
        /// 调用指定对象上具有给定名称的对象属性作为函数，并返回结果。
        /// 如果函数名称为null或为空，则假定当前对象为函数对象。
        /// </summary>
        public InternalHandle Call(string functionName, params InternalHandle[] args)
        {
            return _Handle._Handle.Call(functionName, args);
        }

        // --------------------------------------------------------------------------------------------------------------------
    }

    // ========================================================================================================================

    /// <summary>
    /// 通用版本的'V8NativeObject'允许通过实现'IV8NativeObject'接口来注入自己的类。
    /// </summary>
    /// <typeparam name="T">您自己的类,实现'IV8NativeObject'接口. 如果您能够从'V8NativeObject'继承,则不要使用通用版本.</typeparam>
    public unsafe class V8NativeObject<T> : V8NativeObject
        where T : IV8NativeObject, new()
    {
        // --------------------------------------------------------------------------------------------------------------------

        public V8NativeObject()
            : base(new T())
        {
        }

        // --------------------------------------------------------------------------------------------------------------------
    }

    // ========================================================================================================================
}
